package async;

import java.lang.reflect.Field;
import java.util.concurrent.Executor;
import java.util.concurrent.locks.LockSupport;
import sun.misc.Unsafe;

// 2107 ms, r = 4995000000 java --enable-preview -Xlog:gc=info,gc+heap=info:gc_loom.log:time async.Loom2Test
public class Loom2Test {
	private static final int N1 = 100;
	private static final int N2 = 100;
	private static final int N3 = 1000;
	private static final int N = N1 * N2 * N3;
	private static int s_i;
	private static Thread t0, t1;

	private static void f3() {
		for (int i = 0; i < N3; i++) {
			s_i = i;
			LockSupport.unpark(t1);
			LockSupport.park();
		}
	}

	private static void f2() {
		for (int i = 0; i < N2; i++)
			f3();
	}

	private static void f1() {
		for (int i = 0; i < N1; i++)
			f2();
	}

	static Unsafe unsafe;
	static long OVERRIDE_OFFSET;

	static {
		try {
			Field theUnsafeField = Unsafe.class.getDeclaredField("theUnsafe");
			theUnsafeField.setAccessible(true);
			unsafe = (Unsafe)theUnsafeField.get(null);
			for (long i = 8; ; i++) {
				if (unsafe.getBoolean(theUnsafeField, i)) {
					theUnsafeField.setAccessible(false);
					if (!unsafe.getBoolean(theUnsafeField, i)) {
						OVERRIDE_OFFSET = i;
						break;
					}
					theUnsafeField.setAccessible(true);
				}
				if (i == 32) // should be enough
					throw new UnsupportedOperationException(System.getProperty("java.version"));
			}
		} catch (ReflectiveOperationException e) {
			throw new RuntimeException(e);
		}
	}

	public static void main(String[] args) throws Exception {
		long t = System.nanoTime();
		long[] r = new long[1];
		var vb = Thread.ofVirtual();
		var f = vb.getClass().getDeclaredField("scheduler");
		unsafe.putBoolean(f, OVERRIDE_OFFSET, true); // f.setAccessible(true);
		f.set(vb, (Executor)Runnable::run);
		t0 = vb.unstarted(Loom2Test::f1);
		t1 = vb.unstarted(() -> {
			long r1 = 0;
			for (int i = 0; i < N; ++i) {
				LockSupport.park();
				r1 += s_i;
				LockSupport.unpark(t0);
			}
			r[0] = r1;
		});
		t0.start();
		t1.start();
		t1.join();
		System.out.println((System.nanoTime() - t) / 1_000_000 + " ms, r = " + r[0]);
	}
}
